﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;
namespace Assembler
{
    public class Assembler
    {   


        public Dictionary<string, int> symTable {get; set;}
        public List<Instruction> instructions { get; set; }
        public Dictionary<string, int> baseOpcode;
        public string machineCode { get; set; }
 
        private StreamReader input { get; set; }

        public Assembler(StreamReader sr)
        {
            this.input = sr;
            this.symTable = new Dictionary<string, int>();
            this.instructions = new List<Instruction>();
            this.baseOpcode = new Dictionary<string, int>();
            SetupOpcodes();
            GetInstructions();
        }

        /**
         * initializes the baseOpcode dictionary which contains
         * key value pairs with the name of an operation along with
         * an optional extra character to check if it's indirect and
         * the base machine code for that instruction. For example:
         * key = "AND" value = "0x0000", or key = "ANDI" value = "0x8000"
         * The memory address / io or register reference opcode is added
         * later. The HEX is stored as a base 10 integer.
         **/
        private void SetupOpcodes()
        {
            /** 
             * mem reference instructions, start with 0x(0-6)000 with I'
             * or 0x(8-E)000 with I
             **/
            baseOpcode.Add("AND", 0x0000);
            baseOpcode.Add("ANDI", 0x8000);
            baseOpcode.Add("ADD", 0x1000);
            baseOpcode.Add("ADDI", 0x9000);
            baseOpcode.Add("LDA", 0x2000);
            baseOpcode.Add("LDAI", 0xA000);
            baseOpcode.Add("STA", 0x3000);
            baseOpcode.Add("STAI", 0xB000);
            baseOpcode.Add("BUN", 0x4000);
            baseOpcode.Add("BUNI", 0xC000);
            baseOpcode.Add("BSA", 0x5000);
            baseOpcode.Add("BSAI", 0xD000);
            baseOpcode.Add("ISZ", 0x6000);
            baseOpcode.Add("ISZI", 0xE000);

            //all register instructions start with D7I' or 0x7000
            baseOpcode.Add("REG", 0x7000);
            //all register instructions start with D7I or 0xF000
            baseOpcode.Add("IO", 0xF000);
        }
        /**
         * Two pass assembly after Instructions parsed. 
         * First pass builds symbol table (list of label/address)
         * Second pass generates machine code.
         * Formats output into .mem CedarLogic RAM file to given output file name
         **/
        public void assemble()
        {
            BuildSymbolTable();
            GenerateMachineCode();
        }

        public void GetInstructions()
        {
            string currLine;
            Instruction newInstruction;
            while (!input.EndOfStream)
            {
                currLine = input.ReadLine();
                newInstruction = Instruction.parseInstruction(currLine);
                instructions.Add(newInstruction);
            }
            input.BaseStream.Position = 0;
        }
        private void BuildSymbolTable()
        {
            int lineCounter = 0;
            string addr;
            int addrHex;
            foreach (Instruction i in instructions)
            {
                //instructions address in memory
                i.instrAddress = lineCounter;

                if (i.label != Instruction.EMPTY_STRING)
                {
                    symTable.Add(i.label, lineCounter);
                }

                //if org we branch linecounter
                if (i.opcode == "ORG")
                {
                    addr = i.refAddress;
                    //converts to hex value in int32 form so 100 is
                    //to print as hex use .toString("x");
                    addrHex = Convert.ToInt32(addr, 16);
                    lineCounter = addrHex;
                }
                else
                {
                    lineCounter += Instruction.INSTRUCTION_SIZE;
                }

            }
        }
        private void GenerateMachineCode()
        {
            string result = "";
            foreach (Instruction i in instructions)
            {
                int baseCode;
                int operand;
                int machineInstr = 0;
                string opString = i.opcode;
                if (i.indirect)
                {
                    opString += "I";
                }

                //replace operand address if it is a label
                if (symTable.ContainsKey(i.refAddress))
                {
                    i.refAddress = symTable[i.refAddress].ToString("x");
                }

                if (baseOpcode.ContainsKey(opString))
                {
                    baseCode = baseOpcode[opString];
                    operand = Convert.ToInt32(i.refAddress, 16);
                    //OR top 4 bits to address bits
                    machineInstr = baseCode | operand;
                }
                else
                {
                    if (opString == "DEC")
                    {
                        i.refAddress = i.refAddress.Trim();
                        int addressNumeric = Convert.ToInt32(i.refAddress, 10);
                        if (addressNumeric > 0)
                        {
                            //change to hex
                            i.refAddress = Convert.ToInt32(i.refAddress, 16).ToString();
                        }
                        else //need to get 2's complement
                        {
                            addressNumeric *= -1;
                            addressNumeric++;
                            i.refAddress = addressNumeric.ToString("x");
                        }
                    }
                }

                result += i.instrAddress.ToString("x") + " : " + machineInstr.ToString("x") + "\n";


            }
            Console.Write(result);
            machineCode = result;
        }
    }
}
